package com.hlkj.warboot.configurer.other;

import com.hlkj.warboot.configurer.util.ResponseUtil;
import com.hlkj.warboot.configurer.util.base.BaseAccessLimitUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;

/**
 * @Description: TODO 接口防刷拦截器
 * @Author: 李星 lixing_java@163.com
 * @Date: 2020/2/20 20:39
 */
public class AccessLimitIntercept implements HandlerInterceptor {
    // 通过构造函数注入redisDao
    private final RedisTemplate redisTemplate;

    public AccessLimitIntercept(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /*
     * @Description 前置拦截
     * @Author 李星
     * @Date 2020/2/20 20:43
     * @Param [request, response, handler]
     * @return boolean
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            BaseAccessLimitUtil accessLimit = handlerMethod.getMethodAnnotation(BaseAccessLimitUtil.class); // 获取方法中的AccessLimit注解
            if (accessLimit != null) {
                // TODO =============================================1、对接口本身进行拦截，默认接口在【60】秒内被访问上限【60】次
                int interfaceSeconds = accessLimit.interfaceSeconds(); // 限流区间，每60秒做一次限流
                int interfaceMaxCount = accessLimit.interfaceMaxCount(); // 当前接口在一个限流区间内的访问上限
                String requestUrl = request.getRequestURI(); // 接口地址
                // 设置key（接口地址）的value递增1
                Long requestUrlCount = redisTemplate.opsForValue().increment(requestUrl, 1);
                // 接口在当前限流区间内被首次访问，设置key（接口地址）在interfaceSeconds秒后过期
                if (requestUrlCount == 1) {
                    redisTemplate.expire(requestUrl, interfaceSeconds, TimeUnit.SECONDS);
                }
                // 接口在当前限流区间内被访问的次数上限，提示用户多少秒后再来访问
                if (requestUrlCount > interfaceMaxCount) {
                    String message = MessageFormat.format("【{0}】在 {1} 秒内被访问超限，距离下次访问剩余 {2} 秒",
                            requestUrl, interfaceSeconds, redisTemplate.getExpire(requestUrl, TimeUnit.SECONDS));
                    ResponseUtil.outputJson(response, ResponseUtil.fail(message));
                    return false;
                }
                // TODO ======================================2、对访问接口的ip进行拦截，默认每个ip在【60】秒内访问接口上限【10】次
                int ipSeconds = accessLimit.ipSeconds();
                int ipMaxCount = accessLimit.ipMaxCount();
                String ip = request.getRemoteAddr(); // 当前请求的ip
                // 设置key（ip地址）的value递增1
                Long ipCount = redisTemplate.opsForValue().increment(ip, 1);
                // ip在当前限流区间内首次访问该接口，设置key（ip地址）在ipSeconds秒后过期
                if (ipCount == 1) {
                    redisTemplate.expire(ip, ipSeconds, TimeUnit.SECONDS);
                    return true;
                }
                // ip在当前限流区间内访问该接口的次数上限，提示用户多少秒后再来访问
                if (ipCount > ipMaxCount) {
                    redisTemplate.opsForValue().decrement(requestUrl, 1);
                    String message = MessageFormat.format("【{0}】在 {1} 秒内访问 【{2}】 超限，距离下次访问剩余 {4} 秒",
                            ip, ipSeconds, requestUrl, ipCount, redisTemplate.getExpire(ip, TimeUnit.SECONDS));
                    ResponseUtil.outputJson(response, ResponseUtil.fail(message));
                    return false;
                }
            }
            return true;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
